﻿namespace FastColoredTextBoxNS
{
    using System;
    using System.Collections;
    using System.Collections.Generic;
    using System.Drawing;
    using System.IO;
    using System.Text;

    /// <summary>
    /// 	This class contains the source text (chars and styles).
    /// 	It stores a text lines, the manager of commands, undo/redo stack, styles.
    /// </summary>
    public class TextSource : IList<Line>, IDisposable
    {
        protected readonly List<Line> lines = new List<Line>();

        protected LinesAccessor linesAccessor;

        private int lastLineUniqueId;

        public CommandManager Manager { get; set; }

        private FastColoredTextBox currentTB;

        /// <summary>
        /// 	Styles
        /// </summary>
        public readonly Style[] Styles;

        /// <summary>
        /// 	Occurs when line was inserted/added
        /// </summary>
        public event EventHandler<LineInsertedEventArgs> LineInserted;

        /// <summary>
        /// 	Occurs when line was removed
        /// </summary>
        public event EventHandler<LineRemovedEventArgs> LineRemoved;

        /// <summary>
        /// 	Occurs when text was changed
        /// </summary>
        public event EventHandler<TextChangedEventArgs> TextChanged;

        /// <summary>
        /// 	Occurs when recalc is needed
        /// </summary>
        public event EventHandler<TextChangedEventArgs> RecalcNeeded;

        /// <summary>
        /// 	Occurs when recalc wordwrap is needed
        /// </summary>
        public event EventHandler<TextChangedEventArgs> RecalcWordWrap;

        /// <summary>
        /// 	Occurs before text changing
        /// </summary>
        public event EventHandler<TextChangingEventArgs> TextChanging;

        /// <summary>
        /// 	Occurs after CurrentTB was changed
        /// </summary>
        public event EventHandler CurrentTBChanged;

        /// <summary>
        /// 	Current focused FastColoredTextBox
        /// </summary>
        public FastColoredTextBox CurrentTB
        {
            get
            {
                return this.currentTB;
            }
            set
            {
                if (this.currentTB == value)
                {
                    return;
                }
                this.currentTB = value;
                this.OnCurrentTBChanged();
            }
        }

        public virtual void ClearIsChanged()
        {
            foreach (var line in this.lines)
            {
                line.IsChanged = false;
            }
        }

        public virtual Line CreateLine()
        {
            return new Line(this.GenerateUniqueLineId());
        }

        private void OnCurrentTBChanged()
        {
            if (this.CurrentTBChanged != null)
            {
                this.CurrentTBChanged(this, EventArgs.Empty);
            }
        }

        /// <summary>
        /// 	Default text style
        /// 	This style is using when no one other TextStyle is not defined in Char.style
        /// </summary>
        public TextStyle DefaultStyle { get; set; }

        public TextSource(FastColoredTextBox currentTB)
        {
            this.CurrentTB = currentTB;
            this.linesAccessor = new LinesAccessor(this);
            this.Manager = new CommandManager(this);

            if (Enum.GetUnderlyingType(typeof(StyleIndex)) == typeof(UInt32))
            {
                this.Styles = new Style[32];
            }
            else
            {
                this.Styles = new Style[16];
            }

            this.InitDefaultStyle();
        }

        public virtual void InitDefaultStyle()
        {
            this.DefaultStyle = new TextStyle(null, null, FontStyle.Regular);
        }

        public virtual Line this[int i]
        {
            get
            {
                return this.lines[i];
            }
            set
            {
                throw new NotImplementedException();
            }
        }

        public virtual bool IsLineLoaded(int iLine)
        {
            return this.lines[iLine] != null;
        }

        /// <summary>
        /// 	Text lines
        /// </summary>
        public virtual IList<string> GetLines()
        {
            return this.linesAccessor;
        }

        public IEnumerator<Line> GetEnumerator()
        {
            return this.lines.GetEnumerator();
        }

        IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return (this.lines as IEnumerator);
        }

        public virtual int BinarySearch(Line item, IComparer<Line> comparer)
        {
            return this.lines.BinarySearch(item, comparer);
        }

        public virtual int GenerateUniqueLineId()
        {
            return this.lastLineUniqueId++;
        }

        public virtual void InsertLine(int index, Line line)
        {
            this.lines.Insert(index, line);
            this.OnLineInserted(index);
        }

        public virtual void OnLineInserted(int index)
        {
            this.OnLineInserted(index, 1);
        }

        public virtual void OnLineInserted(int index, int count)
        {
            if (this.LineInserted != null)
            {
                this.LineInserted(this, new LineInsertedEventArgs(index, count));
            }
        }

        public virtual void RemoveLine(int index)
        {
            this.RemoveLine(index, 1);
        }

        public virtual bool IsNeedBuildRemovedLineIds
        {
            get
            {
                return this.LineRemoved != null;
            }
        }

        public virtual void RemoveLine(int index, int count)
        {
            var removedLineIds = new List<int>();
            //
            if (count > 0)
            {
                if (this.IsNeedBuildRemovedLineIds)
                {
                    for (int i = 0; i < count; i++)
                    {
                        removedLineIds.Add(this[index + i].UniqueId);
                    }
                }
            }
            //
            this.lines.RemoveRange(index, count);

            this.OnLineRemoved(index, count, removedLineIds);
        }

        public virtual void OnLineRemoved(int index, int count, List<int> removedLineIds)
        {
            if (count > 0)
            {
                if (this.LineRemoved != null)
                {
                    this.LineRemoved(this, new LineRemovedEventArgs(index, count, removedLineIds));
                }
            }
        }

        public virtual void OnTextChanged(int fromLine, int toLine)
        {
            if (this.TextChanged != null)
            {
                this.TextChanged(this, new TextChangedEventArgs(Math.Min(fromLine, toLine), Math.Max(fromLine, toLine)));
            }
        }

        public class TextChangedEventArgs : EventArgs
        {
            public int iFromLine;

            public int iToLine;

            public TextChangedEventArgs(int iFromLine, int iToLine)
            {
                this.iFromLine = iFromLine;
                this.iToLine = iToLine;
            }
        }

        public virtual int IndexOf(Line item)
        {
            return this.lines.IndexOf(item);
        }

        public virtual void Insert(int index, Line item)
        {
            this.InsertLine(index, item);
        }

        public virtual void RemoveAt(int index)
        {
            this.RemoveLine(index);
        }

        public virtual void Add(Line item)
        {
            this.InsertLine(this.Count, item);
        }

        public virtual void Clear()
        {
            this.RemoveLine(0, this.Count);
        }

        public virtual bool Contains(Line item)
        {
            return this.lines.Contains(item);
        }

        public virtual void CopyTo(Line[] array, int arrayIndex)
        {
            this.lines.CopyTo(array, arrayIndex);
        }

        /// <summary>
        /// 	Lines count
        /// </summary>
        public virtual int Count
        {
            get
            {
                return this.lines.Count;
            }
        }

        public virtual bool IsReadOnly
        {
            get
            {
                return false;
            }
        }

        public virtual bool Remove(Line item)
        {
            int i = this.IndexOf(item);
            if (i >= 0)
            {
                this.RemoveLine(i);
                return true;
            }
            else
            {
                return false;
            }
        }

        public virtual void NeedRecalc(TextChangedEventArgs args)
        {
            if (this.RecalcNeeded != null)
            {
                this.RecalcNeeded(this, args);
            }
        }

        public virtual void OnRecalcWordWrap(TextChangedEventArgs args)
        {
            if (this.RecalcWordWrap != null)
            {
                this.RecalcWordWrap(this, args);
            }
        }

        public virtual void OnTextChanging()
        {
            string temp = null;
            this.OnTextChanging(ref temp);
        }

        public virtual void OnTextChanging(ref string text)
        {
            if (this.TextChanging != null)
            {
                var args = new TextChangingEventArgs { InsertingText = text };
                this.TextChanging(this, args);
                text = args.InsertingText;
                if (args.Cancel)
                {
                    text = string.Empty;
                }
            }
            ;
        }

        public virtual int GetLineLength(int i)
        {
            return this.lines[i].Count;
        }

        public virtual bool LineHasFoldingStartMarker(int iLine)
        {
            return !string.IsNullOrEmpty(this.lines[iLine].FoldingStartMarker);
        }

        public virtual bool LineHasFoldingEndMarker(int iLine)
        {
            return !string.IsNullOrEmpty(this.lines[iLine].FoldingEndMarker);
        }

        public virtual void Dispose()
        {
            ;
        }

        public virtual void SaveToFile(string fileName, Encoding enc)
        {
            using (var sw = new StreamWriter(fileName, false, enc))
            {
                for (int i = 0; i < this.Count - 1; i++)
                {
                    sw.WriteLine(this.lines[i].Text);
                }

                sw.Write(this.lines[this.Count - 1].Text);
            }
        }
    }
}